package net.qiqbframework.modelling.repository;

import lombok.extern.slf4j.Slf4j;
import net.qiqbframework.config.Configuration;
import net.qiqbframework.config.ConfigurationException;
import net.qiqbframework.eventsourcing.EventStorageEngine;
import net.qiqbframework.loadhanding.JdbcAggregate;
import net.qiqbframework.messaging.task.CurrentMessageTask;
import net.qiqbframework.messaging.task.MessageTask;
import net.qiqbframework.modelling.domain.Aggregate;
import net.qiqbframework.modelling.domain.AggregateModel;
import net.qiqbframework.modelling.domain.BizIdentifierModel;
import net.qiqbframework.tracing.NoOpSpanFactory;
import org.javers.core.diff.Diff;
import org.javers.core.diff.changetype.PropertyChange;

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.function.Consumer;

import static net.qiqbframework.common.BuilderUtils.assertNonNull;

@Slf4j
public abstract class AbstractRepository<T, A extends Aggregate<T>> implements Repository<T> {


    private final String aggregatesKey = this + "_AGGREGATES";

    private final AggregateModel<T> aggregateModel;

    protected final EventStorageEngine eventStorageEngine;

    protected final RepositorySpanFactory spanFactory;

    private final Configuration configuration;

    protected AbstractRepository(Builder<T> builder) {
        builder.validate();
        this.aggregateModel = builder.aggregateModel;
        this.spanFactory = builder.spanFactory;
        this.eventStorageEngine = builder.eventStorageEngine;
        this.configuration = builder.configuration;


    }


    @Override
    public A newInstance(@Nonnull Callable<T> factoryMethod) throws Exception {
        final A aggregate = newInstance(factoryMethod, a -> {

        });
        // 解析
        final MessageTask<?> messageTask = CurrentMessageTask.get();
        for (Map.Entry<BizIdentifierModel<?>, Object> bizIdentifieEntry : aggregateModel.getBizIdentifiers(aggregate.aggregateRoot()).entrySet()) {
            final Object bizIdentifier = bizIdentifieEntry.getValue();
            managedAggregates(messageTask).put(bizIdentifieEntry.getKey().modelName() + ":" + bizIdentifier, aggregate);
        }
        return aggregate;
    }

    @Override
    public A newInstance(@Nonnull Callable<T> factoryMethod, @Nonnull Consumer<Aggregate<T>> initMethod) throws Exception {


        A aggregate;
        try {
            aggregate = doCreateNew(factoryMethod);
        } catch (Exception e) {
            log.warn("Exception occurred while trying to creat e an aggregate.", e);
            throw e;
        }

        initMethod.accept(aggregate);
        prepareForCommit(aggregate);
        return aggregate;
    }

    protected Map<String, A> managedAggregates(MessageTask<?> messageTask) {
        return messageTask.root().getOrComputeResource(aggregatesKey, s -> new HashMap<>());
    }

    protected abstract A doCreateNew(Callable<T> factoryMethod) throws Exception;

    @Override
    public Aggregate<T> loadFromCache(AggregateBizIdentifierVoucher bizIdentifierVoucher) {
        final MessageTask<?> messageTask = CurrentMessageTask.get();
        return managedAggregates(messageTask).get(bizIdentifierVoucher.getKey());
    }

    @Override
    public Map<AggregateBizIdentifierVoucher, Aggregate<T>> batchLoad(List<AggregateBizIdentifierVoucher> bizIdentifierVouchers) {
        final MessageTask<?> messageTask = CurrentMessageTask.get();
        Map<AggregateBizIdentifierVoucher, Aggregate<T>> result = new HashMap<>(bizIdentifierVouchers.size());
        // 根据 name 分组
        Map<String, List<AggregateBizIdentifierVoucher>> sameNameBizIdentifierVouchers = new HashMap<>();
        for (AggregateBizIdentifierVoucher bizIdentifierVoucher : bizIdentifierVouchers) {
            sameNameBizIdentifierVouchers
                    .computeIfAbsent(bizIdentifierVoucher.getBizIdentifierName(), k -> new ArrayList<>())
                    .add(bizIdentifierVoucher);
        }
        for (Map.Entry<String, List<AggregateBizIdentifierVoucher>> entry : sameNameBizIdentifierVouchers.entrySet()) {
            // 过滤从缓存中获取
            final List<AggregateBizIdentifierVoucher> preLoadBizIdentifierVouchers = entry.getValue().stream().filter(i -> {
                final A cashedAggregate = managedAggregates(messageTask).get(i.getKey());
                return cashedAggregate == null;
            }).toList();

            final List<A> aggregates = spanFactory.createLoadSpan(entry.getKey())
                    .runSupplier(() -> {

                        List<A> loadingAggregates = doLoads(preLoadBizIdentifierVouchers, aggregateModel, true);
                        for (A loadingAggregate : loadingAggregates) {
                            if (loadingAggregate == null) {
                                throw new IllegalStateException("加载聚合根对象失败");
                            }
                            prepareForCommit(loadingAggregate);
                        }
                        return loadingAggregates;
                    });
            // 解析ID，

            for (A aggregate : aggregates) {
                for (Map.Entry<BizIdentifierModel<?>, Object> bizIdentifieEntry : aggregateModel.getBizIdentifiers(aggregate.aggregateRoot()).entrySet()) {
                    final Object bizIdentifier = bizIdentifieEntry.getValue();
                    managedAggregates(messageTask).put(bizIdentifieEntry.getKey().modelName() + ":" + bizIdentifier, aggregate);
                }
            }
        }
        for (AggregateBizIdentifierVoucher bizIdentifierVoucher : bizIdentifierVouchers) {
            final A a = managedAggregates(messageTask).get(bizIdentifierVoucher.getKey());
            if (a == null) {
                log.warn("未加载到对应的聚合根对象");
            }
            result.put(bizIdentifierVoucher, a);
        }

        return result;
    }

    /**
     * 具有相同类型的ID
     *
     * @param bizIdentifierVouchers
     * @return
     */
    public abstract List<A> doLoads(@Nonnull List<AggregateBizIdentifierVoucher> bizIdentifierVouchers, AggregateModel<T> aggregateModel, boolean enableCaching);




    protected void prepareForCommit(A aggregate) {
        final MessageTask<?> messageTask = CurrentMessageTask.get();
        if (messageTask != null) {
            final List<A> prepareForCommitAggregates = messageTask.getOrComputeResource("_aggregates@" + aggregateModel.modelName(), t -> {
                final List<A> aggregates = new ArrayList<>();
                messageTask.onPrepareCommit(r -> {
                    // 验证bizId 是否存在。
                    // 所有ID集合
                    Map<BizIdentifierModel<?>, List<Object>> bizIdentifierModelObjectMap = new HashMap<>();
                    for (A a : aggregates) {
                        // 快照和对象比较。如果biz ID修改了，则需要查询是否存在
                        if (a instanceof JdbcAggregate<?> jdbcAggregate) {
                            if (a.isNew()) {
                                final Map<BizIdentifierModel<?>, Object> bizIdentifiers = aggregateModel.getBizIdentifiers(a.aggregateRoot());
                                // bizIdentifiers 都是新的

                                for (Map.Entry<BizIdentifierModel<?>, Object> entry : bizIdentifiers.entrySet()) {
                                    bizIdentifierModelObjectMap.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()).add(entry.getValue());
                                }
                                // 查询
                                // todo 优化，如果确定ID唯一值，则不需要进行验证
                                for (Map.Entry<BizIdentifierModel<?>, List<Object>> entry : bizIdentifierModelObjectMap.entrySet()) {
                                    final ArrayList<AggregateBizIdentifierVoucher> bizIdentifierVouchers = new ArrayList<>();
                                    for (Object bizId : entry.getValue()) {
                                        bizIdentifierVouchers.add(new AggregateBizIdentifierVoucher(entry.getKey().modelName(), bizId));
                                    }
                                    final List<A> existedAggregate = doLoads(bizIdentifierVouchers, aggregateModel, true);
                                    // 如果存在，直接报已经存在
                                    if (!existedAggregate.isEmpty()) {
                                        for (int i = 0; i < existedAggregate.size(); i++) {

                                            if (existedAggregate.get(i) != null) {
                                                final AggregateBizIdentifierVoucher bizIdentifierVoucher = bizIdentifierVouchers.get(i);
                                                throw new IllegalStateException("当前id[%s]已经存在".formatted(bizIdentifierVoucher));
                                            }
                                        }

                                    }
                                }
                            } else {
                                if (!a.isDeleted()) {
                                    final Diff compare = configuration.javers().compare(jdbcAggregate.getSnapshotAggregateRoot(), a.aggregateRoot());
                                    if (compare.hasChanges()) {
                                        for (BizIdentifierModel<?> bizIdentifierModel : aggregateModel.allBizIdentifierModels()) {
                                            final String bizName = bizIdentifierModel.modelName();
                                            final List<PropertyChange> propertyChanges = compare.getPropertyChanges(bizName);
                                            if (!propertyChanges.isEmpty()) {
                                                for (PropertyChange propertyChange : propertyChanges) {
                                                    if (propertyChange.getPropertyNameWithPath().equals(bizName)) {
                                                        bizIdentifierModelObjectMap.computeIfAbsent(bizIdentifierModel, k -> new ArrayList<>()).add(propertyChange.getRight());
                                                    }
                                                }
                                            }
                                        }

                                    }
                                    // 进行数据库查询，而且不能是从缓存中获取
                                    for (Map.Entry<BizIdentifierModel<?>, List<Object>> entry : bizIdentifierModelObjectMap.entrySet()) {
                                        final ArrayList<AggregateBizIdentifierVoucher> bizIdentifierVouchers = new ArrayList<>();
                                        for (Object bizId : entry.getValue()) {
                                            bizIdentifierVouchers.add(new AggregateBizIdentifierVoucher(entry.getKey().modelName(), bizId));
                                        }
                                        final List<A> existedAggregate = doLoads(bizIdentifierVouchers, aggregateModel, true);
                                        // 如果存在，直接报已经存在
                                        if (!existedAggregate.isEmpty()) {
                                            for (int i = 0; i < existedAggregate.size(); i++) {

                                                if (existedAggregate.get(i) != null) {
                                                    final AggregateBizIdentifierVoucher bizIdentifierVoucher = bizIdentifierVouchers.get(i);
                                                    throw new IllegalStateException("当前id[%s]已经存在".formatted(bizIdentifierVoucher));
                                                }
                                            }
                                        }
                                    }
                                }
                            }

                        }

                    }


                    doSaves(aggregates);
                });
                return aggregates;
            });
            if (!prepareForCommitAggregates.contains(aggregate)) {
                prepareForCommitAggregates.add(aggregate);
            }

        }

    }


    protected AggregateModel<T> aggregateModel() {
        return aggregateModel;
    }


    protected Class<? extends T> getAggregateType() {
        return aggregateModel.entityClass();
    }


    protected abstract void doSaves(List<A> aggregate);


    public static abstract class Builder<T> {


        private AggregateModel<T> aggregateModel;
        private EventStorageEngine eventStorageEngine;
        private Configuration configuration;

        private RepositorySpanFactory spanFactory = DefaultRepositorySpanFactory.builder()
                .spanFactory(NoOpSpanFactory.INSTANCE)
                .build();

        protected Builder(AggregateModel<T> aggregateModel) {
            this.aggregateModel = aggregateModel;
        }


        /**
         * Sets the {@link AggregateModel} of generic type {@code T}, describing the structure of the aggregate this
         * {@link Repository} will store.
         *
         * @param aggregateModel the {@link AggregateModel} of generic type {@code T} of the aggregate this {@link
         *                       Repository} will store
         * @return the current Builder instance, for fluent interfacing
         */
        public Builder<T> aggregateModel(@Nonnull AggregateModel<T> aggregateModel) {
            assertNonNull(aggregateModel, "AggregateModel may not be null");
            this.aggregateModel = aggregateModel;
            return this;
        }

        public Builder<T> eventStorageEngine(EventStorageEngine eventStorageEngine) {
            assertNonNull(eventStorageEngine, "eventStorageEngine may not be null");
            this.eventStorageEngine = eventStorageEngine;
            return this;
        }


        public Builder<T> spanFactory(RepositorySpanFactory spanFactory) {
            assertNonNull(spanFactory, "SpanFactory may not be null");
            this.spanFactory = spanFactory;
            return this;
        }

        protected void validate() throws ConfigurationException {

            assertNonNull(
                    aggregateModel,
                    "No aggregateType is set, whilst either it or the AggregateModel is a hard requirement"
            );
        }

        public void configuration(Configuration c) {
            this.configuration = c;
        }
    }
}
